/*
 * GraphicView.java
 *
 * Created on 13 maart 2008, 14:35
 *
 * @author wijnand.schepens@hogent.be
 */
package view.GUI.graphic;

import controller.Constants;
import controller.IListener;
import controller.commands.BasicCommand;
import controller.commands.ClearSelectionCommand;
import controller.commands.DeleteCommand;
import controller.commands.ExecuteThread;
import controller.commands.MacroCommand;
import controller.commands.SetSelectedCommand;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.Transparency;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JOptionPane;
import javax.swing.border.LineBorder;
import model.codebehind.CodeModel;
import model.graphic.GraphicModel;
import model.graphic.objects.GBoxNode;
import model.graphic.objects.GConnection;
import model.graphic.objects.GInput;
import model.graphic.objects.Graphical;
import model.graphic.objects.GTrigger;
import model.graphic.objects.GNode;
import model.graphic.objects.RectangleGraphical;
import view.GUI.LabelReader;
import view.GUI.actions.RemoveConnectionAction;
import view.GUI.sidepanels.CustomArgument;
import view.GUI.popup.ViewPopup;

public class GraphicView extends JLayeredPane implements MouseListener, MouseMotionListener, KeyListener {

    private GraphicModel gmodel;
    private CodeModel cmodel;
    private ViewPopup popup;
    private Point dragStartPoint = null;
    private Point dragEndPoint = null;
    private Rectangle dragRectangle = null;
    private Constants cons = new Constants();
    private Grid grid;
    boolean tempBoolean = true;
    boolean makeConnection = false;
    private List<IListener> observers = new ArrayList<IListener>();
    private boolean firstPaint = true;
    private boolean gridon = true;
    private boolean snapon = true;
    private GTrigger triggerFound = null;
    private GInput inputFound = null;
    private ExecuteThread executeThread;
    private boolean pause = false;
    private int currentTriggerPos;
    public static final Dimension SIZE = new Dimension(1000,1000);

    public GraphicView(GraphicModel gmodel, CodeModel cmodel) {
        this.gmodel = gmodel;
        this.cmodel = cmodel;
        this.addMouseListener(this);
        this.addMouseMotionListener(this);
        this.addKeyListener(this);
        this.setOpaque(false);
        this.popup = new ViewPopup(this, cmodel, gmodel ,(JFrame) this.getParent());
        this.grid = new Grid();
        this.setBorder(new LineBorder(Color.BLACK));
        this.setOpaque(true);
        this.setBackground(Color.WHITE);
        this.setPreferredSize(SIZE);
        this.setMaximumSize(SIZE);
        this.setLayout(null); //vo jcomponents
    }

    public GInput getInputFound() {
        return inputFound;
    }

    public GTrigger getTriggerFound() {
        return triggerFound;
    }

    public void setInputFound(GInput inputFound) {
        this.inputFound = inputFound;
    }

    public void setTriggerFound(GTrigger triggerFound) {
        this.triggerFound = triggerFound;
    }

    public ViewPopup getPopup() {
        return popup;
    }

    public boolean isGridOn() {
        return gridon;
    }

    public boolean isSnapped() {
        return snapon;
    }

    public int getCurrentTriggerPosIncr(){
        return currentTriggerPos++;
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;

        if (isGridOn()) {
            grid.setDimension(this.getWidth(), this.getHeight());
            grid.paint(g2);
        }

        if(gmodel.getAllObjects().size() == 0){
             g2.drawString(LabelReader.getInstance().getString("emptyGrid"), 50, 215);
        }

        currentTriggerPos = 0;

        for (Graphical c : gmodel.getAllObjectNoInternals()){
            c.paint(g2);
        }

        if (dragRectangle != null) {
            g2.setColor(Color.RED);
            Stroke oldStroke = g2.getStroke();
            g2.setStroke(cons.getDashedStroke());
            g2.draw(dragRectangle);
            g2.setStroke(oldStroke);
        }
    }

    public List<IListener> getObservers() {
        return observers;
    }

    public void addObserver(IListener l) {
        observers.add(l);
    }

    public void removeObserver(IListener l) {
        observers.remove(l);
    }

    public void notifyObservers(BasicCommand arg) {
        for (IListener l : observers) {
            l.update(arg);
        }
    }

    //source = http://www.daniweb.com/forums/thread136630.html
    public void saveToJPG(File file) {
        Image img = this.createImage(getWidth(), getHeight());
        Graphics g = img.getGraphics();
        paint(g);
        try {
            ImageIO.write(toBufferedImage(img), "jpg", file);
            JOptionPane.showMessageDialog(null,
                    LabelReader.getInstance().getString("ImageSavedTo")
                    + " " + file.toString()
                    ,LabelReader.getInstance().getString("Completed"),
                    JOptionPane.INFORMATION_MESSAGE);
            g.dispose();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public void setExecuteThread(ExecuteThread et) {
        this.executeThread = et;
    }

    public ExecuteThread getExecuteThread() {
        return executeThread;
    }

    public boolean isPaused() {
        return pause;
    }

    public void setGridMode(boolean b) {
        gridon = b;
    }

    public void setPause(boolean b) {
        pause = b;
    }

    public void setSnapMode(boolean b) {
        snapon = b;
    }

    public void addAllNodesFromModel(){//needed to make nodes clickable
      this.removeAll();//needed to avoid illegalArgumentException
      for(Graphical g: this.getBoxnodeParent().getTransmitterObjects()){
            this.add(g);
      }
      for(Graphical g : gmodel.getObjects() ){
        if(g.getClass().isAssignableFrom(GBoxNode.class)){
            this.add(g,10);
            for(Graphical t: ((GBoxNode)g).getTransmitterObjects()){
                this.add(t);
            }
        }else this.add(g);
      }
    }

    public ArrayList<Graphical> getAllNodes() {
        return gmodel.getAllNodes();
    }

    private BufferedImage toBufferedImage(Image image) {
        if (image instanceof BufferedImage) {
            return (BufferedImage) image;
        }
        image = new ImageIcon(image).getImage();

        BufferedImage bimage = null;
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        try {
            int transparency = Transparency.OPAQUE;
            GraphicsDevice gs = ge.getDefaultScreenDevice();
            GraphicsConfiguration gc = gs.getDefaultConfiguration();
            bimage = gc.createCompatibleImage(image.getWidth(null), image.getHeight(null), transparency);
        } catch (HeadlessException e) {
        }

        if (bimage == null) {
            int type = BufferedImage.TYPE_INT_RGB;
            bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
        }
        Graphics g = bimage.createGraphics();
        g.drawImage(image, 0, 0, image.getWidth(null), image.getHeight(null), null);
        g.dispose();
        return bimage;
    }

    public void mouseClicked(MouseEvent e) {
    }
    /*
     * selectes the node or handle zo make it draggable
     * */

    public void mousePressed(MouseEvent e) {
        this.dragStartPoint = e.getPoint();
        this.dragEndPoint = e.getPoint();
        notifyObservers(new ClearSelectionCommand());
        notifyObservers(new CustomArgument("tabRight", 1));
        this.repaint();
    }

    public void mouseReleased(MouseEvent e) {
        if (dragRectangle != null) {
            boolean selection = false;
            try {
                ArrayList<BasicCommand> comm = new ArrayList<BasicCommand>();
                for (Graphical g : gmodel.getAllNodes()) {
                    if (GNode.class.isAssignableFrom(g.getClass()) || GBoxNode.class.isAssignableFrom(g.getClass())) {
                        if (g.isContainedIn(dragRectangle)) {
                            selection = true;
                            this.notifyObservers(new SetSelectedCommand(g, true));
                        }
                    }
                }
                if (selection) {
                    notifyObservers(new CustomArgument("tabRight", 0));
                }
            } catch (Exception ex) {
                //
            }
        }
        if (e.isPopupTrigger()) {
            getPopup().menuPanel();
            getPopup().getPopup().show(this, e.getX(), e.getY());
        }
        this.dragRectangle = null;
        this.repaint();
    }

    public void mouseEntered(MouseEvent e) {
    }

    public void mouseExited(MouseEvent e) {
    }

    public void mouseMoved(MouseEvent e) {
        gmodel.setMouseLocation(new Point(e.getX(), e.getY()));

    }

    public void mouseDragged(MouseEvent e) {
        int x = e.getX(), y = e.getY();
        int xr = Math.min(dragStartPoint.x, x);
        int yr = Math.min(dragStartPoint.y, y);
        int wr = Math.abs(dragStartPoint.x - x);
        int hr = Math.abs(dragStartPoint.y - y);
        this.dragRectangle = new Rectangle(xr, yr, wr, hr);
        this.dragEndPoint = e.getPoint();
        this.repaint();

    }

    public void giveAttention() {
        this.repaint();
        this.updateUI();
    }

    public GBoxNode getBoxnodeParent(){
        return gmodel.getBoxnodeParent();
    }

    /**
     * @return the mouseLocation
     */
    public Point getMouseLocation() {
        return gmodel.getMouseLocation();
    }

    public JLayeredPane getLayeredPane() {
        return getRootPane().getLayeredPane();
    }

    public void keyTyped(KeyEvent e) {
        //throw new UnsupportedOperationException("Not supported yet.");
    }

    public void keyPressed(KeyEvent e) {
        //throw new UnsupportedOperationException("Not supported yet.");
    }

    public void keyReleased(KeyEvent e) {
        ArrayList<Graphical> overloop = new ArrayList<Graphical>();
        overloop.addAll(gmodel.getAllNodes());
        if (e.getKeyCode() == KeyEvent.VK_DELETE) {
            for (Graphical g : overloop) {
                RectangleGraphical gn = (RectangleGraphical) g;
                if (gn.isSelected()) {
                    this.notifyObservers(new DeleteCommand(gn));
                    this.notifyObservers(new CustomArgument("tabRight", 1));
                }
                //loop through all the connections of the node
               if(GNode.class.isAssignableFrom(g.getClass())){
                   //the clone is needed to avoid a ConcurrentModificationException
                   ArrayList<GConnection> cons = (ArrayList<GConnection>)((GNode)g).getConns().clone();
                   for(GConnection conn : cons){
                        if(conn.isSelected()){
                            (new RemoveConnectionAction(conn,this)).actionPerformed(null);
                        }
                    }
               }
            }
        } else if (e.isControlDown() && e.getKeyCode() == KeyEvent.VK_A) {
            boolean selection = false;
            try {
                ArrayList<BasicCommand> comm = new ArrayList<BasicCommand>();
                for (Graphical g : overloop) {
                    if (GNode.class.isAssignableFrom(g.getClass()) || GBoxNode.class.isAssignableFrom(g.getClass())) {
                        selection = true;
                        comm.add(new SetSelectedCommand(g, true));
                    }
                }
                this.notifyObservers(new MacroCommand(comm));
                if (selection) {
                    notifyObservers(new CustomArgument("tabRight", 0));
                }
            } catch (Exception ex) {
                //
            }

        }
        this.repaint();
    }

}
